/****************************************************************************
 * Copyright (C) 2009-2010 SciTouch LLC
 * 
 * This file is part of Indigo toolkit.
 * 
 * This file may be distributed and/or modified under the terms of the
 * GNU General Public License version 3 as published by the Free Software
 * Foundation and appearing in the file LICENSE.GPL included in the
 * packaging of this file.
 * 
 * This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE
 * WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
 ***************************************************************************/

#include <stdio.h>
#include <string.h>
#include <ctype.h>

#include "dingo/c/dingo_c.h"

#include "base_cpp/scanner.h"
#include "molecule/sdf_loader.h"
#include "molecule/rdf_loader.h"
#include "base_cpp/output.h"
#include "base_cpp/auto_ptr.h"

void usage (void)
{
   fprintf(stderr,
           "Usage: dingo-render infile.{mol,rxn,smi} outfile.{png,svg,pdf} [parameters]\n"
           "       dingo-render infile.{sdf,rdf,smi} outfile_%%s.{png,svg,pdf} [parameters]\n"
           "       dingo-render infile.{sdf,rdf}.gz outfile_%%s.{png,svg,pdf} [parameters]\n"
           "       dingo-render infile.smi outfile.{mol,rxn} [parameters]\n"
           "       dingo-render infile.smi outfile.{sdf} [parameters]\n"
           "       dingo-render - SMILES outfile.{png,svg,pdf} [parameters]\n"
           "       dingo-render - SMILES outfile.{mol,rxn} [parameters]\n"
           "\nParameters:\n"
           "-w <number>\n"
           "   Picture width in pixels\n"
           "-h <number>\n"
           "   Picture height in pixels\n"
           "-bond <number>\n"
           "   Average bond length in pixels (conflicts with -w and -h)\n"
           "-marginfactor <number>\n"
           "   Set margin relative to average bond length. Default is 1.0\n"
           "-thickness <number>\n"
           "   Set relative thickness factor. Default is 1.0\n"
           "-hydro <none|terminal|hetero|terminalhetero|all>\n"
           "   Set implicit hydrogen display mode (default is terminalhetero)\n"
           "-label <normal|forceshow|hideterminal|forcehide>\n"
           "   Set atom label display mode (default is normal)\n"
           "-[de]arom\n"
           "   Force [de]aromatization\n"
           "-stereo <old|ext>\n"
           "   Stereogroups display mode (default is 'old')\n"
           "-cdbwsa\n"
           "   Center double bonds which have an adjacent stereo bond (disabled by default)\n"
           "-query\n"
           "   Treat the input as a query molecule or reaction (disabled by default)\n"
           "-id <string>\n"
           "   SDF/RDF field to be put in place of '%%s' in the names of saved files\n"
           "   (default is molecule/reaction number)\n"
           "-comment <string>\n"
           "   Text comment to be put above the molecule or reaction. No default value.\n"
           //"   Two special identifiers can be set as a comment:\n"
           //"      -comment id     use the SDF/RDF field specified with the '-id' key\n"
           //"      -comment name   use the name present in the molecule or reaction\n"
           "-commentsize <number>\n"
           "   Text comment font size factor relative to bond thickness (default 6)\n"
           "-commentpos <top|bottom>\n"
           "   Text comment position (bottom by default)\n"
           "-commentalign <left|center|right>\n"
           "   Text comment alignment (center by default)\n"
           "-coloring <on|off>\n"
           "   Enable/disable coloring (enabled by default)\n"
           "-hlthick\n"
           "   Enable highlighting with thick lines and bold characters\n"
           "-hlcolor <red> <green> <blue>\n"
           "   Enable highlighting with color. Component values must be in range [0..255]\n"
           "-bgcolor <red> <green> <blue>\n"
           "   Set the background color. Component values must be in range [0..255]\n"
           "-basecolor <red> <green> <blue>\n"
           "   Set the default foreground color. Component values must be in range [0..255]\n"
           "-aamcolor <red> <green> <blue>\n"
           "   Set the color of AAM indices. Component values must be in range [0..255]\n"
           "-commentcolor <red> <green> <blue>\n"
           "   Set the color of the comment. Component values must be in range [0..255]\n"
           "-atomnumbers\n"
           "   Show atom numbers (for debugging purposes only)\n"
           "-bondnumbers\n"
           "   Show bond numbers (for debugging purposes only)\n"
           "-help\n"
           "   Print this help message\n"
           "\n"
           "Examples:\n"
           "   dingo-render infile.mol outfile.png -coloring off -arom\n"
           "   dingo-render database.sdf molecule_%%s.png -id cdbregno -thickness 1.1\n"
           "   dingo-render database.smi database.sdf\n"
           "   dingo-render - \"CC.[O-][*-]([O-])=O\" query.png -query\n"
           "   dingo-render - \"OCO>>CC(C)N\" reaction.rxn\n"
           );
}

#define CALL(call) do { if (!call) { fprintf(stderr, "%s\n", dingoGetLastError()); return -2;} } while (0)
#define CALLCONT(call) if (!call) { fprintf(stderr, "%s\n", dingoGetLastError()); continue;} while (0)
#define USAGE() do { usage(); return -1; } while (0)

int parseColor (char *argv[], int i, float *rr, float *gg, float *bb)
{
   int r, g, b;
   
   if (sscanf(argv[i + 1], "%d", &r) != 1 || r < 0 || r > 255)
   {
      fprintf(stderr, "%s is not a valid color index\n", argv[i + 1]);
      return -1;
   }
   if (sscanf(argv[i + 2], "%d", &g) != 1 || g < 0 || g > 255)
   {
      fprintf(stderr, "%s is not a valid color index\n", argv[i + 2]);
      return -1;
   }
   if (sscanf(argv[i + 3], "%d", &b) != 1 || b < 0 || b > 255)
   {
      fprintf(stderr, "%s is not a valid color index\n", argv[i + 3]);
      return -1;
   }

   *rr = r / 255.f;
   *gg = g / 255.f;
   *bb = b / 255.f;
   return 0;
}

int _isMultiline (const char *filename, int *is_reaction)
{
   FILE *f = fopen(filename, "rt");
   int c;

   *is_reaction = 0;
   
   if (f == NULL)
   {
      fprintf(stderr, "Can not open %s for reading\n", filename);
      return -1;
   }

   while ((c = fgetc(f)) != EOF)
   {
      if (c == '>')
         *is_reaction = 1;
      if (c == '\n')
         break;
   }

   if (c == EOF)
      return 0;

   while ((c = fgetc(f)) != EOF)
   {
      if (!isspace(c))
         return 1;
   }

   return 0;
}

int _isReaction (const char *smiles)
{
   return strchr(smiles, '>') != NULL;
}

enum
{
   MODE_SINGLE_MOLECULE,
   MODE_SINGLE_REACTION,
   MODE_MULTILINE_SMILES,
   MODE_SDF,
   MODE_RDF
};

int main (int argc, char *argv[])
{
   const char *outfile;   char outfile_ext[4], infile_ext[7];
   int width = -1, height = -1;
   int bond  = -1;
   int i;
   int mode = -1;
   const char *id = NULL;
   const char *string_to_load = NULL;
   const char *file_to_load = NULL;
   int hydro_set = 0, query_set = 0;

   if (argc <= 2)
      USAGE();

   if (strcmp(argv[1], "-") == 0)
   {
      if (_isReaction(argv[2]))
      {
         CALL(dingoSetInputFormat("rsmiles"));
         mode = MODE_SINGLE_REACTION;
         string_to_load = argv[2];
      }
      else
      {
         CALL(dingoSetInputFormat("smiles"));
         mode = MODE_SINGLE_MOLECULE;
         string_to_load = argv[2];
      }
      if (argc <= 3)
         USAGE();
      i = 3;
   }
   else
   {
      infile_ext[0] = 0;
      if (strlen(argv[1]) >= 4 && argv[1][strlen(argv[1]) - 4] == '.')
      {
         infile_ext[3] = 0;
         strncpy(infile_ext, argv[1] + strlen(argv[1]) - 3, 3);
      }
      else if (strlen(argv[1]) > 7 && argv[1][strlen(argv[1]) - 7] == '.')
      {
         infile_ext[6] = 0;
         strncpy(infile_ext, argv[1] + strlen(argv[1]) - 6, 6);
      }

      if (strcasecmp(infile_ext, "mol") == 0)
      {
         CALL(dingoSetInputFormat("molfile"));
         mode = MODE_SINGLE_MOLECULE;
         file_to_load = argv[1];
      }
      else if (strcasecmp(infile_ext, "rxn") == 0)
      {
         CALL(dingoSetInputFormat("rxnfile"));
         mode = MODE_SINGLE_REACTION;
         file_to_load = argv[1];
      }
      else if (strcasecmp(infile_ext, "smi") == 0)
      {
         int reaction;
         
         if (_isMultiline(argv[1], &reaction))
            mode = MODE_MULTILINE_SMILES;
         else
         {
            if (reaction)
            {
               CALL(dingoSetInputFormat("rsmiles"));
               mode = MODE_SINGLE_REACTION;
               file_to_load = argv[1];
            }
            else
            {
               CALL(dingoSetInputFormat("smiles"));
               mode = MODE_SINGLE_MOLECULE;
               file_to_load = argv[1];
            }
         }
      }
      else if (strcasecmp(infile_ext, "sdf") == 0 || strcasecmp(infile_ext, "sdf.gz") == 0)
         mode = MODE_SDF;
      else if (strcasecmp(infile_ext, "rdf") == 0 || strcasecmp(infile_ext, "rdf.gz") == 0)
         mode = MODE_RDF;
      else
         USAGE();

      i = 2;
   }

   outfile = argv[i++];

   if (strlen(outfile) < 5 || outfile[strlen(outfile) - 4] != '.')
      USAGE();

   outfile_ext[3] = 0;
   strncpy(outfile_ext, outfile + strlen(outfile) - 3, 3);

   CALL(dingoSetOptionEnabled("coloring", 1));
   CALL(dingoSetOptionEnabled("highlight-color-enabled", 0));
   CALL(dingoSetOptionEnabled("highlight-thickness-enabled", 0));
   CALL(dingoSetOptionEnabled("stereo-old-style", 1));

   for (; i < argc; i++)
   {
      if (strcmp(argv[i], "-w") == 0)
      {
         if (++i == argc)
         {
            fprintf(stderr, "expecting number after -w\n");
            return -1;
         }

         if (sscanf(argv[i], "%d", &width) != 1 || width <= 0)
         {
            fprintf(stderr, "%s is not a valid width\n", argv[i]);
            return -1;
         }
      }
      else if (strcmp(argv[i], "-h") == 0)
      {
         if (++i == argc)
         {
            fprintf(stderr, "expecting number after -h\n");
            return -1;
         }

         if (sscanf(argv[i], "%d", &height) != 1 || height <= 0)
         {
            fprintf(stderr, "%s is not a valid height\n", argv[i]);
            return -1;
         }
      }
      else if (strcmp(argv[i], "-marginfactor") == 0)
      {
         float margin_factor;

         if (++i == argc)
         {
            fprintf(stderr, "expecting number after -marginfactor\n");
            return -1;
         }

         if (sscanf(argv[i], "%f", &margin_factor) != 1 || margin_factor < 0)
         {
            fprintf(stderr, "%s is not a valid margin factor\n", argv[i]);
            return -1;
         }
         CALL(dingoSetOptionFloat("margin-factor", margin_factor));
      }
      else if (strcmp(argv[i], "-thickness") == 0)
      {
         float rt;

         if (++i == argc)
         {
            fprintf(stderr, "expecting number after -thickness\n");
            return -1;
         }

         if (sscanf(argv[i], "%f", &rt) != 1 || rt < 0)
         {
            fprintf(stderr, "%s is not a valid relative thickness\n", argv[i]);
            return -1;
         }
         CALL(dingoSetOptionFloat("relative-thickness", rt));
      }
      else if (strcmp(argv[i], "-bond") == 0)
      {
         if (++i == argc)
         {
            fprintf(stderr, "expecting number after -bond\n");
            return -1;
         }

         if (sscanf(argv[i], "%d", &bond) != 1 || bond <= 0)
         {
            fprintf(stderr, "%s is not a valid bond length\n", argv[i]);
            return -1;
         }
      }
      else if (strcmp(argv[i], "-coloring") == 0)
      {
         if (++i == argc)
         {
            fprintf(stderr, "expecting 'on' or 'off' after -coloring\n");
            return -1;
         }

         if (strcasecmp(argv[i], "on") == 0)
            CALL(dingoSetOptionEnabled("coloring", 1));
         else if (strcasecmp(argv[i], "off") == 0)
            CALL(dingoSetOptionEnabled("coloring", 0));
         else
         {
            fprintf(stderr, "expecting 'on' or 'off' after -coloring\n");
            return -1;
         }
      }
      else if (strcmp(argv[i], "-hlthick") == 0)
      {
         CALL(dingoSetOptionEnabled("highlight-thickness-enabled", 1));
      }
      else if (strcmp(argv[i], "-hlcolor") == 0)
      {
         float r, g, b;

         if (i + 3 >= argc)
         {
            fprintf(stderr, "expecting 3 numbers after -hlcolor\n");
            return -1;
         }

         if (parseColor(argv, i, &r, &g, &b) != 0)
            return -1;

         CALL(dingoSetOptionEnabled("highlight-color-enabled", 1));
         CALL(dingoSetOptionColor("highlight-color", r, g, b));
         i += 3;
      }
      else if (strcmp(argv[i], "-bgcolor") == 0)
      {
         float r, g, b;

         if (i + 3 >= argc)
         {
            fprintf(stderr, "expecting 3 numbers after -bgcolor\n");
            return -1;
         }

         if (parseColor(argv, i, &r, &g, &b) != 0)
            return -1;
         CALL(dingoSetOptionColor("background-color", r, g, b));
         i += 3;
      }
      else if (strcmp(argv[i], "-basecolor") == 0)
      {
         float r, g, b;

         if (i + 3 >= argc)
         {
            fprintf(stderr, "expecting 3 numbers after -basecolor\n");
            return -1;
         }

         if (parseColor(argv, i, &r, &g, &b) != 0)
            return -1;
         CALL(dingoSetOptionColor("base-color", r, g, b));
         i += 3;
      }
      else if (strcmp(argv[i], "-aamcolor") == 0)
      {
         float r, g, b;

         if (i + 3 >= argc)
         {
            fprintf(stderr, "expecting 3 numbers after -aamcolor\n");
            return -1;
         }

         if (parseColor(argv, i, &r, &g, &b) != 0)
            return -1;

         CALL(dingoSetOptionColor("aam-color", r, g, b));
         i += 3;
      }
      else if (strcmp(argv[i], "-hydro") == 0)
      {
         if (++i == argc)
         {
            fprintf(stderr, "expecting an identifier after -hydro\n");
            return -1;
         }
         CALL(dingoSetOption("implicit-hydrogen-mode", argv[i]));
         hydro_set = 1;
      }
      else if (strcmp(argv[i], "-label") == 0)
      {
         if (++i == argc)
         {
            fprintf(stderr, "expecting an identifier after -label\n");
            return -1;
         }
         CALL(dingoSetOption("label-mode", argv[i]));
      }
      else if (strcmp(argv[i], "-arom") == 0)
      {
         CALL(dingoSetOption("aromatization", "arom"));
      }
      else if (strcmp(argv[i], "-dearom") == 0)
      {
         CALL(dingoSetOption("aromatization", "dearom"));
      }
      else if (strcmp(argv[i], "-stereo") == 0)
      {
         if (++i == argc)
         {
            fprintf(stderr, "expecting an identifier after -stereo\n");
            return -1;
         }

         if (strcasecmp(argv[i], "old") == 0)
            CALL(dingoSetOptionEnabled("stereo-old-style", 1));
         else if (strcasecmp(argv[i], "ext") == 0)
            CALL(dingoSetOptionEnabled("stereo-old-style", 0));
         else
         {
            fprintf(stderr, "expecting 'old' or 'ext' after -stereo\n");
            return -1;
         }
      }
      else if (strcmp(argv[i], "-cdbwsa") == 0)
      {
         CALL(dingoSetOptionEnabled("center-double-bond-when-stereo-adjacent", 1));
      }
      else if (strcmp(argv[i], "-query") == 0)
      {
         CALL(dingoSetOptionEnabled("query", 1));
         query_set = 1;
      }
      else if (strcmp(argv[i], "-id") == 0)
      {
         if (++i == argc)
         {
            fprintf(stderr, "expecting an identifier after -id\n");
            return -1;
         }

         id = argv[i];
      }
      else if (strcmp(argv[i], "-comment") == 0)
      {
         if (++i == argc)
         {
            fprintf(stderr, "expecting an identifier after -comment\n");
            return -1;
         }

         CALL(dingoSetComment(argv[i]));
      }
      else if (strcmp(argv[i], "-commentsize") == 0)
      {
         int commentsize;

         if (++i == argc)
         {
            fprintf(stderr, "expecting number after -commentsize\n");
            return -1;
         }

         if (sscanf(argv[i], "%d", &commentsize) != 1 || commentsize <= 0)
         {
            fprintf(stderr, "%s is not a valid font size\n", argv[i]);
            return -1;
         }
         CALL(dingoSetCommentFontSize(commentsize));
      }
      else if (strcmp(argv[i], "-commentcolor") == 0)
      {
         float r, g, b;

         if (i + 3 >= argc)
         {
            fprintf(stderr, "expecting 3 numbers after -commentcolor\n");
            return -1;
         }

         if (parseColor(argv, i, &r, &g, &b) != 0)
            return -1;

         CALL(dingoSetOptionColor("comment-color", r, g, b));
         i += 3;
      }
      else if (strcmp(argv[i], "-commentalign") == 0)
      {
         if (++i == argc)
         {
            fprintf(stderr, "expecting an identifier after -commentalign\n");
            return -1;
         }
         CALL(dingoSetOption("comment-alignment", argv[i]));
      }
      else if (strcmp(argv[i], "-commentpos") == 0)
      {
         if (++i == argc)
         {
            fprintf(stderr, "expecting an identifier after -commentpos\n");
            return -1;
         }
         CALL(dingoSetOption("comment-position", argv[i]));
      }
      else if (strcmp(argv[i], "-atomnumbers") == 0)
      {
         CALL(dingoSetOptionEnabled("atom-ids-visible", 1));
      }
      else if (strcmp(argv[i], "-bondnumbers") == 0)
      {
         CALL(dingoSetOptionEnabled("bond-ids-visible", 1));
      }
      else if (strcmp(argv[i], "-help") == 0)
      {
         usage();
         return 0;
      }
      else
         USAGE();
   }

   if (width <= 0 && height <= 0)
   {
      if (bond > 0)
         CALL(dingoSetBondLength((float)bond));
   }
   else if (width <= 0 || height <= 0)
   {
      fprintf(stderr, "-w and -h should be specified both or neither\n");
      return -1;
   }
   else
   {
      if (bond > 0)
      {
         fprintf(stderr, "-bond conflicts with -w and -h\n");
         return -1;
      }
      CALL(dingoSetImageSize(width, height));
   }

   if (hydro_set && query_set)
   {
      fprintf(stderr, "-hydro conflicts with -query (implicit hydrogens do not exist in queries)\n");
   }

   if (mode == MODE_SINGLE_MOLECULE)
   {
      int save_result = 1;

      if (file_to_load != NULL)
         CALL(dingoLoadMolFromFile(file_to_load));
      else
         CALL(dingoLoadMolFromString(string_to_load));

      if (id != NULL)
      {
         fprintf(stderr, "on single input, setting '-id' is not allowed\n");
         return -1;
      }

      CALL(dingoSetOutputFile(outfile));
      
      if (strcmp(outfile_ext, "mol") == 0 || strcmp(outfile_ext, "sdf") == 0)
      {
         CALL(dingoSetOutputFormat("mol"));
         CALL(dingoLayout());
      }
      else if (strcmp(outfile_ext, "rxn") == 0)
      {
         fprintf(stderr, "reaction output specified for molecule input\n");
         return -1;
      }
      else if (strcmp(outfile_ext, "rdf") == 0)
      {
         fprintf(stderr, "saving RDF files not supported\n");
         return -1;
      }
      else
      {
         CALL(dingoSetOutputFormat(outfile_ext));
         CALL(dingoRender());
         save_result = 0;
      }
   }
   else if (mode == MODE_SINGLE_REACTION)
   {
      int save_result = 1;

      if (file_to_load != NULL)
         CALL(dingoLoadReactionFromFile(file_to_load));
      else
         CALL(dingoLoadReactionFromString(string_to_load));

      if (id != NULL)
      {
         fprintf(stderr, "on single input, setting '-id' is not allowed\n");
         return -1;
      }

      CALL(dingoSetOutputFile(outfile));

      if (strcmp(outfile_ext, "mol") == 0 || strcmp(outfile_ext, "sdf") == 0)
      {
         fprintf(stderr, "molecule output specified for reaction input\n");
         return -1;
      }
      else if (strcmp(outfile_ext, "rxn") == 0)
      {
         CALL(dingoSetOutputFormat("rxn"));
         CALL(dingoLayout());
      }
      else if (strcmp(outfile_ext, "rdf") == 0)
      {
         fprintf(stderr, "saving RDF files not supported\n");
         return -1;
      }
      else
      {
         CALL(dingoSetOutputFormat(outfile_ext));
         CALL(dingoRender());
         save_result = 0;
      }
   }
   else
   {
      AutoPtr<FileOutput> layout_output;

      if (strcmp(outfile_ext, "sdf") == 0)
      {
         //CALL(dingoSetOutputFormat("mol"));
         layout_output.reset(new FileOutput(outfile));
      }
      else if (strcmp(outfile_ext, "rdf") == 0)
      {
         fprintf(stderr, "saving RDF files not supported\n");
         return -1;
      }
      else if (strcmp(outfile_ext, "mol") == 0)
         CALL(dingoSetOutputFormat("mol"));
      else if (strcmp(outfile_ext, "rxn") == 0)
         CALL(dingoSetOutputFormat("rxn"));
      else
         CALL(dingoSetOutputFormat(outfile_ext));

      if (mode == MODE_MULTILINE_SMILES)
      {
         if (strstr(outfile, "%s") == NULL && layout_output.get() == 0)
         {
            fprintf(stderr, "on multiple input, output file name must have '%%s'\n");
            return -1;
         }

         try
         {
            FileScanner scanner(argv[1]);
            Array<char> smiles;
            int i = 0;
            char number[100];
            char outfilename[4096];

            while (!scanner.isEOF())
            {
               scanner.readString(smiles, true);

               if (smiles.size() <= 1)
                  continue;

               i++;

               if (_isReaction(smiles.ptr()))
               {
                  if (strcmp(outfile_ext, "sdf") == 0)
                  {
                     printf("warning: saving reaction to SDF file\n");
                     //fprintf(stderr,
                     //        "%s seems to be a reaction SMILES, "
                     //        "while SDF output is specified rather than RDF\n",
                     //        smiles.ptr());
                     //continue;
                  }
                  if (strcmp(outfile_ext, "mol") == 0)
                  {
                     printf("%s seems to be a reaction SMILES, skipping\n", smiles.ptr());
                     continue;
                  }
                  CALLCONT(dingoSetInputFormat("rsmiles"));
                  CALLCONT(dingoLoadReactionFromString(smiles.ptr()));
                  if (layout_output.get() != 0)
                     CALL(dingoSetOutputFormat("rxn"));

               }
               else
               {
                  /*if (strcmp(outfile_ext, "rdf") == 0)
                  {
                     fprintf(stderr,
                             "%s seems to be a molecule SMILES, "
                             "while RDF output is specified rather than SDF\n",
                             smiles.ptr());
                     continue;
                  }*/
                  if (strcmp(outfile_ext, "rxn") == 0)
                  {
                     printf("%s seems to be a molecule SMILES, skipping\n", smiles.ptr());
                     continue;
                  }
                  CALLCONT(dingoSetInputFormat("smiles"));
                  CALLCONT(dingoLoadMolFromString(smiles.ptr()));
                  if (layout_output.get() != 0)
                     CALL(dingoSetOutputFormat("mol"));
               }

               if (layout_output.get() != 0)
               {
                  byte *buf;
                  int buf_size;

                  printf("saving item #%d...\n", i);

                  CALLCONT(dingoLayout());
                  CALLCONT(dingoGetResult(&buf, &buf_size));
                  layout_output->write(buf, buf_size);
                  layout_output->printfCR("$$$$");
               }
               else
               {
                  snprintf(number, sizeof(number), "%d", i);
                  snprintf(outfilename, sizeof(outfilename), outfile, number);

                  printf("saving %s...\n", outfilename);
                  CALLCONT(dingoSetOutputFile(outfilename));
                  if (strcmp(outfile_ext, "mol") == 0 || strcmp(outfile_ext, "rxn") == 0)
                  {
                     CALLCONT(dingoLayout());
                  }
                  else
                  {
                     CALLCONT(dingoRender());
                  }
               }
            }
         }
         catch (Exception &e)
         {
            fprintf(stderr, "%s\n", e.message());
         }
      }
      else if (mode == MODE_SDF)
      {
         try
         {
            FileScanner scanner(argv[1]);
            SdfLoader loader(scanner);

            int i = 0;
            char id_str[2048];
            char outfilename[4096];

            while (!loader.isEOF())
            {
               loader.readNext();
               i++;

               if (id != NULL)
               {
                  if (loader.properties.at2(id) == 0 || loader.properties.at(id).size() <= 1)
                  {
                     fprintf(stderr, "item #%d does not have %s, skipping\n", i, id);
                     continue;
                  }
                  id_str[sizeof(id_str) - 1] = 0;
                  strncpy(id_str, loader.properties.at(id).ptr(), sizeof(id_str) - 1);
               }
               else
                  snprintf(id_str, sizeof(id_str), "%d", i);

               if (layout_output.get() == 0)
               {
                  if (strstr(outfile, "%s") == NULL)
                  {
                     if (i > 1)
                     {
                        fprintf(stderr, "on multiple input, output file name must have '%%s'\n");
                        return -1;
                     }
                     strncpy(outfilename, outfile, sizeof(outfilename));
                  }
                  else
                     snprintf(outfilename, sizeof(outfilename), outfile, id_str);
                  printf("saving %s...\n", outfilename);
               }

               CALLCONT(dingoSetInputFormat("molfile"));
               CALLCONT(dingoLoadMolFromBuffer((const byte *)loader.data.ptr(), loader.data.size()));

               if (layout_output.get() != 0)
               {
                  byte *buf;
                  int buf_size;

                  CALLCONT(dingoLayout());
                  CALLCONT(dingoGetResult(&buf, &buf_size));
                  layout_output->write(buf, buf_size);
                  layout_output->printfCR("$$$$");
               }
               else
               {
                  CALLCONT(dingoSetOutputFile(outfilename));
                  if (strcmp(outfile_ext, "mol") == 0 || strcmp(outfile_ext, "rxn") == 0)
                  {
                     CALLCONT(dingoLayout());
                  }
                  else
                  {
                     CALLCONT(dingoRender());
                  }
               }
            }
         }
         catch (Exception &e)
         {
            fprintf(stderr, "%s\n", e.message());
         }
      }
      else if (mode == MODE_RDF)
      {
         try
         {
            FileScanner scanner(argv[1]);
            RdfLoader loader(scanner);

            int i = 0;
            char id_str[2048];
            char outfilename[4096];

            while (!loader.isEOF())
            {
               loader.readNext();
               i++;

               if (id != NULL)
               {
                  if (loader.properties.at2(id) == 0 || loader.properties.at(id).size() <= 1)
                  {
                     fprintf(stderr, "item #%d does not have %s, skipping\n", i, id);
                     continue;
                  }
                  id_str[sizeof(id_str) - 1] = 0;
                  strncpy(id_str, loader.properties.at(id).ptr(), sizeof(id_str) - 1);
               }
               else
                  snprintf(id_str, sizeof(id_str), "%d", i);

               if (layout_output.get() == 0)
               {
                  if (strstr(outfile, "%s") == NULL)
                  {
                     if (i > 1)
                     {
                        fprintf(stderr, "on multiple input, output file name must have '%%s'\n");
                        return -1;
                     }
                     strncpy(outfilename, outfile, sizeof(outfilename));
                  }
                  else
                     snprintf(outfilename, sizeof(outfilename), outfile, id_str);
                  printf("saving %s...\n", outfilename);
               }

               if (loader.isMolecule())
               {
                  if (strcmp(outfile_ext, "rxn") == 0)
                  {
                     printf("can not save a molecule to RXN file, skipping...\n");
                     continue;
                  }
                  CALLCONT(dingoSetInputFormat("molfile"));
                  CALLCONT(dingoLoadMolFromBuffer((const byte *)loader.data.ptr(), loader.data.size()));
               }
               else
               {
                  if (strcmp(outfile_ext, "mol") == 0)
                  {
                     printf("can not save a reaction to molfile, skipping...\n");
                     continue;
                  }
                  CALLCONT(dingoSetInputFormat("rxnfile"));
                  CALLCONT(dingoLoadReactionFromBuffer((const byte *)loader.data.ptr(), loader.data.size()));
               }

               if (layout_output.get() != 0)
               {
                  byte *buf;
                  int buf_size;

                  CALLCONT(dingoLayout());
                  CALLCONT(dingoGetResult(&buf, &buf_size));
                  layout_output->write(buf, buf_size);
                  layout_output->printfCR("$$$$");
               }
               else
               {
                  CALLCONT(dingoSetOutputFile(outfilename));
                  if (strcmp(outfile_ext, "mol") == 0 || strcmp(outfile_ext, "rxn") == 0)
                  {
                     CALLCONT(dingoLayout());
                  }
                  else
                  {
                     CALLCONT(dingoRender());
                  }
               }
            }

         }
         catch (Exception &e)
         {
            fprintf(stderr, "%s\n", e.message());
         }
      }
   }
   return 0;
}
